package net.bingosoft.common.struts;


import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import net.bingosoft.common.ObjectRegistry;
import net.bingosoft.common.config.Config;
import net.bingosoft.common.data.ValueBean;
import net.bingosoft.common.monitoring.ServletResource;
import net.bingosoft.common.util.ClassUtil;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.struts.Globals;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ExceptionHandler;
import org.apache.struts.action.RequestProcessor;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ExceptionConfig;
import org.apache.struts.util.RequestUtils;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.beans.factory.BeanFactory;

import bingo.common.annotation.Param;
import bingo.common.annotation.ParamBean;
import bingo.common.annotation.ParamMap;
import bingo.common.utils.CacheabledParameterNameDiscover;

/**
 * 2007-10-29 下午12:35:26
 * Author:yangdongyu(yangdy@bingosoft.net)
 * ReadMe:自定义的请求处理类,实现和Spring集成,实现动态表单,实现smartaction,实现struts配置文件的热修改
 * ChangeLog:
 */
public class CustomizedRequestProcessor extends RequestProcessor{
	private static BeanFactory overrideFactory ;
	private static ServletResource configFiles;
	private static boolean UNDER_DEVELOP = Config.getBoolean("UNDER_DEVELOP",false);
	private static String STRUTS_CONFIG_DIR = Config.getString("STRUTS_CONFIG_DIR","/WEB-INF/struts-config");



	@Override
	protected Action processActionCreate(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws IOException {
		String prefix = mapping.getModuleConfig().getPrefix();
		String path = mapping.getPath();
		String beanName = prefix + path;
		if (log.isDebugEnabled()) {
			log.debug("DelegatingActionProxy with mapping path '" + path + "' and module prefix '" +
					prefix + "' delegating to Spring bean with name [" + beanName + "]");
		}
		if(overrideFactory != null && overrideFactory.containsBean(beanName)){
			return (Action) overrideFactory.getBean(beanName, Action.class);
		}
		return super.processActionCreate(request, response, mapping);
	}


	/**
	 * Set a web-app wide BeanFactory.
	 * @param factory The factory to set.
	 */
	public static void setOverrideBeanFactory(BeanFactory factory){
		CustomizedRequestProcessor.overrideFactory = factory;
	}


	@Override
	protected ActionForm processActionForm(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) {
		// Create (if necessary) a form bean to use
		ActionForm instance = RequestUtils.createActionForm
		(request, mapping, moduleConfig, servlet);
		if (instance == null && mapping.getAttribute() != null) {
			//return null;
			//不要求用户定义表单,我们提供默认实现
			log.debug("no user defined form found,use BaseLazyDynaForm ...");
			instance = new BaseLazyDynaForm();
		}else if(mapping.getAttribute() == null){
			return null;
		}
		// Store the new instance in the appropriate scope
		if (log.isDebugEnabled()) {
			log.debug(" Storing ActionForm bean instance in scope '" +
					mapping.getScope() + "' under attribute key '" +
					mapping.getAttribute() + "'");
		}
		String attribute = mapping.getAttribute();
		if(attribute != null){
			if ("request".equals(mapping.getScope())) {
				request.setAttribute(mapping.getAttribute(), instance);
			} else {
				HttpSession session = request.getSession();
				session.setAttribute(mapping.getAttribute(), instance);
			}
		}
		return (instance);
	}

	/**
	 * <p>Select the mapping used to process the selection path for this request.
	 * If no mapping can be identified, create an error response and return
	 * <code>null</code>.</p>
	 *
	 * @param request The servlet request we are processing
	 * @param response The servlet response we are creating
	 * @param path The portion of the request URI for selecting a mapping
	 *
	 * @exception IOException if an input/output error occurs
	 */
	protected ActionMapping processMapping(HttpServletRequest request,
			HttpServletResponse response,
			String path)
	throws IOException {

		// Is there a mapping for this path?
		ActionMapping mapping = (ActionMapping)
		moduleConfig.findActionConfig(path);

		// If a mapping is found, put it in the request and return it
		if (mapping != null) {
			request.setAttribute(Globals.MAPPING_KEY, mapping);
			return (mapping);
		}

		// Locate the mapping for unknown paths (if any)
		ActionConfig configs[] = moduleConfig.findActionConfigs();
		for (int i = 0; i < configs.length; i++) {
			if (configs[i].getUnknown()) {
				mapping = (ActionMapping) configs[i];
				request.setAttribute(Globals.MAPPING_KEY, mapping);
				return (mapping);
			}
		}



		boolean isProcessed = processNoMappingFound(request, response, path);
		if(isProcessed) return null;

		// No mapping can be found to process this request
		String msg = getInternal().getMessage("processInvalid", path);
		log.error(msg);
		response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);

		return null;
	}
	/**
	 * 没有找到对应ActionConfig时的处理函数
	 * @param request
	 * @param response
	 * @param path
	 * @return
	 * @throws IOException 
	 */
	protected boolean processNoMappingFound(HttpServletRequest request,
			HttpServletResponse response,
			String path) throws IOException{
		boolean isProcessed = false;
		String []requestURIInfo = path.split("/");
		int length = requestURIInfo.length;
		String beanIdOrSmartActionName = requestURIInfo[length-1];
		if(beanIdOrSmartActionName.contains(".")){
			int dotIndex = beanIdOrSmartActionName.lastIndexOf('.');
			String beanId = beanIdOrSmartActionName.substring(0,dotIndex);
			String methodName = beanIdOrSmartActionName.substring(dotIndex+1);
			//spring 优先
			Object smartAction = ObjectRegistry.containsBean(beanId) ? ObjectRegistry.getBean(beanId) : null;
			
			if( smartAction != null){
				try {
					ActionContext.init(request, response);
					callService(smartAction,methodName);    				
					isProcessed = true;
				}  catch (Exception exeception) {
					isProcessed = true;
					log.warn("call ["+smartAction.getClass().getSimpleName()+"] method [" +methodName+"] failed ...  ",exeception);
					try {
						ActionForward forward = processException(request, response,exeception, null, null);
						processForwardConfig(request, response, forward);
					}catch (ServletException se) {
						throw new RuntimeException("exeception happend while processing error ...",se);
					}
				}finally{
					ActionContext.destory();
				}
			}else{
				throw new RuntimeException("No bean with id [" + beanId + "] is found,please check") ;
			}
		}
		return isProcessed;
	}

	/**
	 * <p>Ask our exception handler to handle the exception. Return the
	 * <code>ActionForward</code> instance (if any) returned by the
	 * called <code>ExceptionHandler</code>.</p>
	 *
	 * @param request The servlet request we are processing
	 * @param response The servlet response we are processing
	 * @param exception The exception being handled
	 * @param form The ActionForm we are processing
	 * @param mapping The ActionMapping we are using
	 *
	 * @exception IOException if an input/output error occurs
	 * @exception ServletException if a servlet exception occurs
	 */
	protected ActionForward processException(HttpServletRequest request,
			HttpServletResponse response,
			Exception exception,
			ActionForm form,
			ActionMapping mapping)
	throws IOException, ServletException {
		ExceptionConfig config = null;
		if(mapping != null){
			// Is there a defined handler for this exception?
			config = mapping.findException(exception.getClass());
			if (config == null) {
				log.warn(getInternal().getMessage("unhandledException",
						exception.getClass()));
				if (exception instanceof IOException) {
					throw (IOException) exception;
				} else if (exception instanceof ServletException) {
					throw (ServletException) exception;
				} else {
					throw new ServletException(exception);
				}
			}
		}else{
			Class<?> type = exception.getClass();
			// Check through the entire superclass hierarchy as needed
			while (true) {

				// Check for a locally defined handler
				String name = type.getName();
				// Check for a globally defined handler
				config = moduleConfig.findExceptionConfig(name);
				if (config != null) {
					break;
				}

				// Loop again for our superclass (if any)
				type = type.getSuperclass();
				if (type == null) {
					break;
				}
			}
		}

		// Use the configured exception handling
		try {
			ExceptionHandler handler = (ExceptionHandler)
			RequestUtils.applicationInstance(config.getHandler());
			return (handler.execute(exception, config, mapping, form,
					request, response));
		} catch (Exception e) {
			throw new ServletException(e);
		}

	}
	/**
	 * 调用服务类方法
	 * @param target
	 * @param methodName
	 * @throws Throwable 
	 */
	protected void callService(Object target,String methodName) {
		try {
			List<Method> methods = ClassUtil.getMethods(target.getClass(), methodName);
			if(methods.size() > 1){
				throw new RuntimeException("More than one method with name "+ methodName + " is found,please check ...");
			}else{
				Method method = methods.get(0);
				Class[] parameterTypes = method.getParameterTypes();
				List params = new ArrayList();
				if(parameterTypes.length >0 ){
					Object paramValue;
					HttpServletRequest request = ActionContext.getRequest();
					Annotation[][] annotationArray = method.getParameterAnnotations();
					for ( int index = 0; index < annotationArray.length;index ++) {
						String paramName = null;
						Annotation[] annotations = annotationArray[index];
						paramValue = null;
						for (Annotation annotation : annotations) {
							if (annotation instanceof Param) {
								paramName = ((Param) annotation).value();
								paramValue = ConvertUtils.convert(request.getParameter(paramName), parameterTypes[index]);
							} else if (annotation instanceof ParamBean) {
								if(parameterTypes[index] == ValueBean.class){
									paramValue = new ValueBean(getParameterMap(request));
								}else{
									paramValue = parameterTypes[index].newInstance();
									RequestUtils.populate(paramValue, request);
								}

							}else if (annotation instanceof ParamMap) {
								paramValue = getParameterMap(request);
							}
						}
						//处理不带注解的request和response参数
						if(paramValue == null){
							if(parameterTypes[index] == HttpServletRequest.class){
								paramValue = ActionContext.getRequest();
							}else if(parameterTypes[index] == HttpServletResponse.class){
								paramValue = ActionContext.getResponse();
							}else if(parameterTypes[index] == String.class || ClassUtil.isPrimitiveOrWrapper(parameterTypes[index])){
								try{
									paramName = CacheabledParameterNameDiscover.getMethodParameterNames(method)[index];
									//get param from request
									paramValue = ConvertUtils.convert(request.getParameter(paramName), parameterTypes[index]);
								}catch (Throwable e) {
									//ignore ex
								}
							}
						}
						params.add(paramValue);
					}
				}

				Object[] args = params.toArray();

				if(target instanceof MethodBeforeAdvice){
					((MethodBeforeAdvice)target).before(method, args, target);
				}
				method.invoke(target,args );
			}

		} catch (Throwable e) {
			//should wap ?? 
			throw new RuntimeException(e);
		}
	}

	/**
	 * 获取请求中的参数表
	 * @param request
	 * @return
	 */
	protected Map<String,Object> getParameterMap(HttpServletRequest request){
		Map<String,Object> paramMap = new HashMap<String,Object>();
		String name;
		//get url param
		Enumeration<?> names = request.getParameterNames();
		while (names.hasMoreElements()) {
			name = (String) names.nextElement();
			String[] s = request.getParameterValues(name);
			if (s != null) {
				if (s.length > 1) {
					paramMap.put(name, s);
				}else{
					paramMap.put(name, s[0]);
				}
			}
		}
		//get attribute param
		Enumeration attributeNames = request.getAttributeNames();
		while (names.hasMoreElements()) {
			name = attributeNames.nextElement().toString();
			paramMap.put(name, request.getAttribute(name));
		}
		// 以http body方式提交的参数
		try {
			String charset = request.getCharacterEncoding();
			if (charset == null)
				charset = "UTF-8";
			BufferedReader in = new BufferedReader(new InputStreamReader(request.getInputStream(), charset));
			// Read the request
			CharArrayWriter data = new CharArrayWriter();
			char buf[] = new char[4096];
			int ret;
			while ((ret = in.read(buf, 0, 4096)) != -1)
				data.write(buf, 0, ret);

			// URL解码
			String content = URLDecoder.decode(data.toString().trim(), charset);
			// 组装参数
			if (content != "") {
				String[] paramPairs = content.split("&");
				String[] kv;
				for (String p : paramPairs) {
					kv = p.split("=");
					if (kv.length > 1)
						paramMap.put(kv[0], kv[1]);
				}
			}

			data.close();
			in.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return paramMap;
	}






	/**
	 * <p>General-purpose preprocessing hook that can be overridden as required
	 * by subclasses. Return <code>true</code> if you want standard processing
	 * to continue, or <code>false</code> if the response has already been
	 * completed. The default implementation does nothing.</p>
	 *
	 * @param request The servlet request we are processing
	 * @param response The servlet response we are creating
	 */
	protected boolean processPreprocess(HttpServletRequest request,
			HttpServletResponse response) {
		if(UNDER_DEVELOP){
			log.debug("检测struts的配置文件是否被修改...");
			//检查配置文件有没有被修改,如果修改了,重新加载配置文件
			synchronized (this) {
				if(configFiles == null){
					configFiles = new ServletResource(this.servlet.getServletContext(),STRUTS_CONFIG_DIR);
				}
				if(configFiles.isChanged()){
					reloadConfig();
				}
			}
		}

		return super.processPreprocess(request, response);
	}
	/**
	 * 重新加载Struts的所有配置文件及资源文件
	 *
	 */
	private void reloadConfig(){
		try{
			log.debug("检测到Struts配置文件修改,重新加载Struts的配置文件...");
			this.servlet.init();
			log.debug("重新加载Struts的配置文件完成...");
		}catch (Exception e) {
			log.warn("重新加载配置文件失败...",e);
		}

	}
}
